<!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>&#x80a1;&#x7968;&#x76d1;&#x63a7;&#x7cfb;&#x7edf;&#x9700;&#x6c42;&#x6587;&#x6863;</title>
            <style>
/* From extension vscode.github */
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

.vscode-dark img[src$=\#gh-light-mode-only],
.vscode-light img[src$=\#gh-dark-mode-only],
.vscode-high-contrast:not(.vscode-high-contrast-light) img[src$=\#gh-light-mode-only],
.vscode-high-contrast-light img[src$=\#gh-dark-mode-only] {
	display: none;
}

</style>
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css">
<link href="https://cdn.jsdelivr.net/npm/katex-copytex@latest/dist/katex-copytex.min.css" rel="stylesheet" type="text/css">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/markdown.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Microsoft/vscode/extensions/markdown-language-features/media/highlight.css">
<style>
            body {
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
                font-size: 14px;
                line-height: 1.6;
            }
        </style>
        <style>
.task-list-item {
    list-style-type: none;
}

.task-list-item-checkbox {
    margin-left: -20px;
    vertical-align: middle;
    pointer-events: none;
}
</style>
<style>
:root {
  --color-note: #0969da;
  --color-tip: #1a7f37;
  --color-warning: #9a6700;
  --color-severe: #bc4c00;
  --color-caution: #d1242f;
  --color-important: #8250df;
}

</style>
<style>
@media (prefers-color-scheme: dark) {
  :root {
    --color-note: #2f81f7;
    --color-tip: #3fb950;
    --color-warning: #d29922;
    --color-severe: #db6d28;
    --color-caution: #f85149;
    --color-important: #a371f7;
  }
}

</style>
<style>
.markdown-alert {
  padding: 0.5rem 1rem;
  margin-bottom: 16px;
  color: inherit;
  border-left: .25em solid #888;
}

.markdown-alert>:first-child {
  margin-top: 0
}

.markdown-alert>:last-child {
  margin-bottom: 0
}

.markdown-alert .markdown-alert-title {
  display: flex;
  font-weight: 500;
  align-items: center;
  line-height: 1
}

.markdown-alert .markdown-alert-title .octicon {
  margin-right: 0.5rem;
  display: inline-block;
  overflow: visible !important;
  vertical-align: text-bottom;
  fill: currentColor;
}

.markdown-alert.markdown-alert-note {
  border-left-color: var(--color-note);
}

.markdown-alert.markdown-alert-note .markdown-alert-title {
  color: var(--color-note);
}

.markdown-alert.markdown-alert-important {
  border-left-color: var(--color-important);
}

.markdown-alert.markdown-alert-important .markdown-alert-title {
  color: var(--color-important);
}

.markdown-alert.markdown-alert-warning {
  border-left-color: var(--color-warning);
}

.markdown-alert.markdown-alert-warning .markdown-alert-title {
  color: var(--color-warning);
}

.markdown-alert.markdown-alert-tip {
  border-left-color: var(--color-tip);
}

.markdown-alert.markdown-alert-tip .markdown-alert-title {
  color: var(--color-tip);
}

.markdown-alert.markdown-alert-caution {
  border-left-color: var(--color-caution);
}

.markdown-alert.markdown-alert-caution .markdown-alert-title {
  color: var(--color-caution);
}

</style>
        
        </head>
        <body class="vscode-body vscode-light">
            <h1 id="股票监控系统需求文档">股票监控系统需求文档</h1>
<h2 id="项目概述">项目概述</h2>
<p>股票监控系统是一个实时股票数据监控和管理平台，支持添加、删除、查看股票信息，实时获取股票价格数据，并提供历史价格查询功能。系统采用前后端分离架构，后端提供RESTful API，前端提供用户友好的Web界面。</p>
<h2 id="技术栈要求">技术栈要求</h2>
<h3 id="前端技术栈">前端技术栈</h3>
<ul>
<li><strong>框架</strong>: Next.js (React)</li>
<li><strong>样式</strong>: CSS Modules 或 Tailwind CSS</li>
<li><strong>状态管理</strong>: React Hooks (useState, useEffect)</li>
<li><strong>HTTP客户端</strong>: fetch API 或 axios</li>
<li><strong>UI组件</strong>: 自定义组件或UI库（如Ant Design、Material-UI）</li>
</ul>
<h3 id="后端技术栈">后端技术栈</h3>
<ul>
<li><strong>框架</strong>: Next.js API Routes</li>
<li><strong>数据库</strong>: MySQL 8.0</li>
<li><strong>ORM</strong>: Prisma 或原生SQL</li>
<li><strong>外部API</strong>: 腾讯股票API (<a href="https://qt.gtimg.cn/q=">https://qt.gtimg.cn/q=</a>)</li>
<li><strong>定时任务</strong>: Node.js setInterval 或 node-cron</li>
</ul>
<h2 id="核心功能需求">核心功能需求</h2>
<h3 id="1-股票管理功能">1. 股票管理功能</h3>
<h4 id="11-添加股票">1.1 添加股票</h4>
<ul>
<li><strong>功能描述</strong>: 用户可以通过股票代码添加股票到监控列表</li>
<li><strong>输入参数</strong>:
<ul>
<li><code>code</code>: 股票代码（必填），支持6位数字格式（如：000001、600036）</li>
<li><code>name</code>: 股票名称（可选），如不填写系统自动从API获取</li>
</ul>
</li>
<li><strong>业务逻辑</strong>:
<ul>
<li>自动识别股票市场：以0、3开头的为深市（sz前缀），以6、5开头的为沪市（sh前缀）</li>
<li>股票代码去重，同一代码不能重复添加</li>
<li>添加成功后立即获取一次股票数据</li>
</ul>
</li>
<li><strong>API接口</strong>: <code>POST /api/stocks</code></li>
</ul>
<h4 id="12-删除股票">1.2 删除股票</h4>
<ul>
<li><strong>功能描述</strong>: 从监控列表中删除指定股票</li>
<li><strong>输入参数</strong>: <code>id</code> - 股票ID</li>
<li><strong>业务逻辑</strong>:
<ul>
<li>删除股票记录的同时删除所有相关的价格历史记录</li>
<li>需要用户确认操作</li>
</ul>
</li>
<li><strong>API接口</strong>: <code>DELETE /api/stocks/:id</code></li>
</ul>
<h4 id="13-获取股票列表">1.3 获取股票列表</h4>
<ul>
<li><strong>功能描述</strong>: 获取所有已添加的股票列表</li>
<li><strong>返回数据</strong>: 股票基本信息数组</li>
<li><strong>容错处理</strong>: 数据库连接失败时返回缓存数据或空数组</li>
<li><strong>API接口</strong>: <code>GET /api/stocks</code></li>
</ul>
<h3 id="2-股票数据获取功能">2. 股票数据获取功能</h3>
<h4 id="21-实时数据获取">2.1 实时数据获取</h4>
<ul>
<li><strong>数据源</strong>: 腾讯股票API</li>
<li><strong>API格式</strong>: <code>https://qt.gtimg.cn/q={股票代码}</code></li>
<li><strong>数据字段</strong>:
<ul>
<li><code>price</code>: 当前价格</li>
<li><code>openPrice</code>: 开盘价</li>
<li><code>closePrice</code>: 收盘价（交易时间内为null）</li>
<li><code>highPrice</code>: 最高价</li>
<li><code>lowPrice</code>: 最低价</li>
<li><code>yesterdayClosePrice</code>: 昨日收盘价</li>
<li><code>rate</code>: 涨跌幅（基于昨日收盘价计算）</li>
<li><code>timestamp</code>: 数据时间戳</li>
</ul>
</li>
</ul>
<h4 id="22-批量数据获取">2.2 批量数据获取</h4>
<ul>
<li><strong>功能描述</strong>: 支持批量获取多只股票数据，提高性能</li>
<li><strong>批处理</strong>: 每批最多10只股票，避免API限流</li>
<li><strong>错误处理</strong>: 使用Promise.allSettled确保部分失败不影响整体</li>
<li><strong>缓存机制</strong>: 30秒缓存有效期，避免频繁API调用</li>
</ul>
<h4 id="23-数据解析和处理">2.3 数据解析和处理</h4>
<ul>
<li><strong>编码处理</strong>: 使用iconv-lite处理GBK编码的股票名称</li>
<li><strong>数据验证</strong>: 确保所有价格字段为有效数字</li>
<li><strong>时区处理</strong>: 统一使用北京时间（UTC+8）</li>
<li><strong>交易时间判断</strong>: 15:00后设置收盘价</li>
</ul>
<h3 id="3-数据存储功能">3. 数据存储功能</h3>
<h4 id="31-数据库配置要求">3.1 数据库配置要求</h4>
<p><strong>重要说明</strong>：</p>
<ul>
<li>使用现有的MySQL数据库实例</li>
<li>所有时间相关操作必须先验证是否为北京时间（UTC+8）</li>
<li>数据库表名需要与现有字段进行去重检查，避免冲突</li>
<li>支持Docker方式部署</li>
</ul>
<h4 id="32-时间处理规范">3.2 时间处理规范</h4>
<p>所有涉及时间的操作都必须遵循以下规范：</p>
<pre><code class="language-javascript"><span class="hljs-comment">// 时间验证和转换函数</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">validateAndConvertToBeijingTime</span>(<span class="hljs-params">timestamp</span>) {
    <span class="hljs-keyword">const</span> beijingTime = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(timestamp);
    <span class="hljs-keyword">const</span> offset = beijingTime.<span class="hljs-title function_">getTimezoneOffset</span>();
    
    <span class="hljs-comment">// 检查是否为北京时间（UTC+8，即-480分钟）</span>
    <span class="hljs-keyword">if</span> (offset !== -<span class="hljs-number">480</span>) {
        <span class="hljs-comment">// 转换为北京时间</span>
        beijingTime.<span class="hljs-title function_">setMinutes</span>(beijingTime.<span class="hljs-title function_">getMinutes</span>() - offset - <span class="hljs-number">480</span>);
    }
    
    <span class="hljs-keyword">return</span> beijingTime;
}

<span class="hljs-comment">// 获取当前北京时间</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">getCurrentBeijingTime</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>();
    <span class="hljs-keyword">return</span> <span class="hljs-title function_">validateAndConvertToBeijingTime</span>(now);
}

<span class="hljs-comment">// 格式化为MySQL DATETIME格式</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">formatForMySQL</span>(<span class="hljs-params">beijingTime</span>) {
    <span class="hljs-keyword">return</span> beijingTime.<span class="hljs-title function_">toISOString</span>().<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-number">19</span>).<span class="hljs-title function_">replace</span>(<span class="hljs-string">&#x27;T&#x27;</span>, <span class="hljs-string">&#x27; &#x27;</span>);
}
</code></pre>
<h4 id="33-数据库设计">3.3 数据库设计</h4>
<p><strong>表名去重检查</strong><br>
在创建表之前，需要检查现有数据库中的表名，避免冲突：</p>
<pre><code class="language-sql"><span class="hljs-comment">-- 检查现有表名</span>
<span class="hljs-keyword">SHOW</span> TABLES;

<span class="hljs-comment">-- 检查表结构</span>
<span class="hljs-keyword">DESCRIBE</span> existing_table_name;

<span class="hljs-comment">-- 检查字段冲突</span>
<span class="hljs-keyword">SELECT</span> COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT 
<span class="hljs-keyword">FROM</span> INFORMATION_SCHEMA.COLUMNS 
<span class="hljs-keyword">WHERE</span> TABLE_SCHEMA <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;stock_monitor&#x27;</span> 
<span class="hljs-keyword">AND</span> TABLE_NAME <span class="hljs-keyword">IN</span> (<span class="hljs-string">&#x27;stocks&#x27;</span>, <span class="hljs-string">&#x27;stock_prices&#x27;</span>);
</code></pre>
<p><strong>stocks表（需要去重检查）</strong>:</p>
<pre><code class="language-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> stocks (
  id <span class="hljs-type">INT</span> AUTO_INCREMENT <span class="hljs-keyword">PRIMARY</span> KEY,
  code <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">UNIQUE</span>,
  name <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,
  created_at <span class="hljs-type">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span>,
  updated_at <span class="hljs-type">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">ON</span> <span class="hljs-keyword">UPDATE</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span>
) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8mb4 <span class="hljs-keyword">COLLATE</span><span class="hljs-operator">=</span>utf8mb4_unicode_ci;
</code></pre>
<p><strong>stock_prices表（需要去重检查）</strong>:</p>
<pre><code class="language-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> stock_prices (
  id <span class="hljs-type">INT</span> AUTO_INCREMENT <span class="hljs-keyword">PRIMARY</span> KEY,
  stock_id <span class="hljs-type">INT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,
  price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span>,
  open_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>),
  close_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>),
  high_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>),
  low_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>),
  yesterday_close_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>),
  change_amount <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>),
  change_percent <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">5</span>, <span class="hljs-number">2</span>),
  volume <span class="hljs-type">BIGINT</span>,
  <span class="hljs-type">timestamp</span> <span class="hljs-type">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span>,
  beijing_time DATETIME <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;北京时间&#x27;</span>,
  <span class="hljs-keyword">FOREIGN</span> KEY (stock_id) <span class="hljs-keyword">REFERENCES</span> stocks(id) <span class="hljs-keyword">ON</span> <span class="hljs-keyword">DELETE</span> CASCADE,
  INDEX idx_stock_timestamp (stock_id, <span class="hljs-type">timestamp</span>),
  INDEX idx_stock_beijing_time (stock_id, beijing_time)
) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8mb4 <span class="hljs-keyword">COLLATE</span><span class="hljs-operator">=</span>utf8mb4_unicode_ci;
</code></pre>
<h4 id="34-数据库初始化脚本">3.4 数据库初始化脚本</h4>
<p><strong>init.sql</strong></p>
<pre><code class="language-sql"><span class="hljs-comment">-- 设置时区为北京时间</span>
<span class="hljs-keyword">SET</span> time_zone <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;+08:00&#x27;</span>;

<span class="hljs-comment">-- 检查并创建数据库</span>
<span class="hljs-keyword">CREATE</span> DATABASE IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> stock_monitor <span class="hljs-type">CHARACTER</span> <span class="hljs-keyword">SET</span> utf8mb4 <span class="hljs-keyword">COLLATE</span> utf8mb4_unicode_ci;
USE stock_monitor;

<span class="hljs-comment">-- 检查现有表名，避免冲突</span>
<span class="hljs-keyword">SHOW</span> TABLES;

<span class="hljs-comment">-- 如果存在同名表，显示警告信息</span>
<span class="hljs-keyword">SELECT</span> 
    <span class="hljs-keyword">CASE</span> 
        <span class="hljs-keyword">WHEN</span> <span class="hljs-built_in">COUNT</span>(<span class="hljs-operator">*</span>) <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span> <span class="hljs-keyword">THEN</span> CONCAT(<span class="hljs-string">&#x27;警告：发现现有表 &#x27;</span>, GROUP_CONCAT(TABLE_NAME), <span class="hljs-string">&#x27;，请检查字段兼容性&#x27;</span>)
        <span class="hljs-keyword">ELSE</span> <span class="hljs-string">&#x27;无冲突表名，可以安全创建&#x27;</span>
    <span class="hljs-keyword">END</span> <span class="hljs-keyword">AS</span> table_check_result
<span class="hljs-keyword">FROM</span> INFORMATION_SCHEMA.TABLES 
<span class="hljs-keyword">WHERE</span> TABLE_SCHEMA <span class="hljs-operator">=</span> <span class="hljs-string">&#x27;stock_monitor&#x27;</span> 
<span class="hljs-keyword">AND</span> TABLE_NAME <span class="hljs-keyword">IN</span> (<span class="hljs-string">&#x27;stocks&#x27;</span>, <span class="hljs-string">&#x27;stock_prices&#x27;</span>);

<span class="hljs-comment">-- 创建stocks表（如果不存在）</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> stocks (
    id <span class="hljs-type">INT</span> AUTO_INCREMENT <span class="hljs-keyword">PRIMARY</span> KEY,
    code <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">20</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">UNIQUE</span> COMMENT <span class="hljs-string">&#x27;股票代码&#x27;</span>,
    name <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;股票名称&#x27;</span>,
    market <span class="hljs-type">VARCHAR</span>(<span class="hljs-number">10</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;市场标识(sh/sz)&#x27;</span>,
    created_at <span class="hljs-type">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="hljs-string">&#x27;创建时间&#x27;</span>,
    updated_at <span class="hljs-type">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> <span class="hljs-keyword">ON</span> <span class="hljs-keyword">UPDATE</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="hljs-string">&#x27;更新时间&#x27;</span>
) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8mb4 <span class="hljs-keyword">COLLATE</span><span class="hljs-operator">=</span>utf8mb4_unicode_ci COMMENT<span class="hljs-operator">=</span><span class="hljs-string">&#x27;股票基本信息表&#x27;</span>;

<span class="hljs-comment">-- 创建stock_prices表（如果不存在）</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> IF <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> stock_prices (
    id <span class="hljs-type">INT</span> AUTO_INCREMENT <span class="hljs-keyword">PRIMARY</span> KEY,
    stock_id <span class="hljs-type">INT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;股票ID&#x27;</span>,
    price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;当前价格&#x27;</span>,
    open_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) COMMENT <span class="hljs-string">&#x27;开盘价&#x27;</span>,
    close_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) COMMENT <span class="hljs-string">&#x27;收盘价&#x27;</span>,
    high_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) COMMENT <span class="hljs-string">&#x27;最高价&#x27;</span>,
    low_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) COMMENT <span class="hljs-string">&#x27;最低价&#x27;</span>,
    yesterday_close_price <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) COMMENT <span class="hljs-string">&#x27;昨日收盘价&#x27;</span>,
    change_amount <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>) COMMENT <span class="hljs-string">&#x27;涨跌金额&#x27;</span>,
    change_percent <span class="hljs-type">DECIMAL</span>(<span class="hljs-number">5</span>, <span class="hljs-number">2</span>) COMMENT <span class="hljs-string">&#x27;涨跌幅(%)&#x27;</span>,
    volume <span class="hljs-type">BIGINT</span> COMMENT <span class="hljs-string">&#x27;成交量&#x27;</span>,
    <span class="hljs-type">timestamp</span> <span class="hljs-type">TIMESTAMP</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-built_in">CURRENT_TIMESTAMP</span> COMMENT <span class="hljs-string">&#x27;UTC时间戳&#x27;</span>,
    beijing_time DATETIME <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">NULL</span> COMMENT <span class="hljs-string">&#x27;北京时间&#x27;</span>,
    <span class="hljs-keyword">FOREIGN</span> KEY (stock_id) <span class="hljs-keyword">REFERENCES</span> stocks(id) <span class="hljs-keyword">ON</span> <span class="hljs-keyword">DELETE</span> CASCADE,
    INDEX idx_stock_timestamp (stock_id, <span class="hljs-type">timestamp</span>),
    INDEX idx_stock_beijing_time (stock_id, beijing_time),
    INDEX idx_beijing_time (beijing_time)
) ENGINE<span class="hljs-operator">=</span>InnoDB <span class="hljs-keyword">DEFAULT</span> CHARSET<span class="hljs-operator">=</span>utf8mb4 <span class="hljs-keyword">COLLATE</span><span class="hljs-operator">=</span>utf8mb4_unicode_ci COMMENT<span class="hljs-operator">=</span><span class="hljs-string">&#x27;股票价格历史表&#x27;</span>;

<span class="hljs-comment">-- 验证表创建结果</span>
<span class="hljs-keyword">SELECT</span> <span class="hljs-string">&#x27;stocks表创建完成&#x27;</span> <span class="hljs-keyword">AS</span> <span class="hljs-keyword">result</span>;
<span class="hljs-keyword">DESCRIBE</span> stocks;

<span class="hljs-keyword">SELECT</span> <span class="hljs-string">&#x27;stock_prices表创建完成&#x27;</span> <span class="hljs-keyword">AS</span> <span class="hljs-keyword">result</span>;
<span class="hljs-keyword">DESCRIBE</span> stock_prices;

<span class="hljs-comment">-- 插入测试数据（可选）</span>
<span class="hljs-keyword">INSERT</span> IGNORE <span class="hljs-keyword">INTO</span> stocks (code, name, market) <span class="hljs-keyword">VALUES</span> 
(<span class="hljs-string">&#x27;000001&#x27;</span>, <span class="hljs-string">&#x27;平安银行&#x27;</span>, <span class="hljs-string">&#x27;sz&#x27;</span>),
(<span class="hljs-string">&#x27;600036&#x27;</span>, <span class="hljs-string">&#x27;招商银行&#x27;</span>, <span class="hljs-string">&#x27;sh&#x27;</span>),
(<span class="hljs-string">&#x27;000002&#x27;</span>, <span class="hljs-string">&#x27;万科A&#x27;</span>, <span class="hljs-string">&#x27;sz&#x27;</span>);

<span class="hljs-keyword">SELECT</span> <span class="hljs-string">&#x27;测试数据插入完成&#x27;</span> <span class="hljs-keyword">AS</span> <span class="hljs-keyword">result</span>;
<span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> stocks;
</code></pre>
<h4 id="35-部署脚本">3.5 部署脚本</h4>
<p><strong><a href="http://deploy.sh">deploy.sh</a></strong></p>
<pre><code class="language-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-comment"># 股票监控系统部署脚本</span>
<span class="hljs-comment"># 使用方法: ./deploy.sh [环境] [操作]</span>
<span class="hljs-comment"># 环境: dev|prod</span>
<span class="hljs-comment"># 操作: build|start|stop|restart|logs</span>

<span class="hljs-built_in">set</span> -e

<span class="hljs-comment"># 配置变量</span>
ENV=<span class="hljs-variable">${1:-dev}</span>
ACTION=<span class="hljs-variable">${2:-start}</span>
APP_NAME=<span class="hljs-string">&quot;stock-monitor&quot;</span>
DOCKER_COMPOSE_FILE=<span class="hljs-string">&quot;docker-compose.yml&quot;</span>
DOCKER_COMPOSE_PROD_FILE=<span class="hljs-string">&quot;docker-compose.prod.yml&quot;</span>

<span class="hljs-comment"># 颜色输出</span>
RED=<span class="hljs-string">&#x27;\033[0;31m&#x27;</span>
GREEN=<span class="hljs-string">&#x27;\033[0;32m&#x27;</span>
YELLOW=<span class="hljs-string">&#x27;\033[1;33m&#x27;</span>
NC=<span class="hljs-string">&#x27;\033[0m&#x27;</span> <span class="hljs-comment"># No Color</span>

<span class="hljs-comment"># 日志函数</span>
<span class="hljs-function"><span class="hljs-title">log_info</span></span>() {
    <span class="hljs-built_in">echo</span> -e <span class="hljs-string">&quot;<span class="hljs-variable">${GREEN}</span>[INFO]<span class="hljs-variable">${NC}</span> <span class="hljs-variable">$1</span>&quot;</span>
}

<span class="hljs-function"><span class="hljs-title">log_warn</span></span>() {
    <span class="hljs-built_in">echo</span> -e <span class="hljs-string">&quot;<span class="hljs-variable">${YELLOW}</span>[WARN]<span class="hljs-variable">${NC}</span> <span class="hljs-variable">$1</span>&quot;</span>
}

<span class="hljs-function"><span class="hljs-title">log_error</span></span>() {
    <span class="hljs-built_in">echo</span> -e <span class="hljs-string">&quot;<span class="hljs-variable">${RED}</span>[ERROR]<span class="hljs-variable">${NC}</span> <span class="hljs-variable">$1</span>&quot;</span>
}

<span class="hljs-comment"># 检查Docker和docker-compose</span>
<span class="hljs-function"><span class="hljs-title">check_dependencies</span></span>() {
    log_info <span class="hljs-string">&quot;检查依赖...&quot;</span>
    
    <span class="hljs-keyword">if</span> ! <span class="hljs-built_in">command</span> -v docker &amp;&gt; /dev/null; <span class="hljs-keyword">then</span>
        log_error <span class="hljs-string">&quot;Docker未安装，请先安装Docker&quot;</span>
        <span class="hljs-built_in">exit</span> 1
    <span class="hljs-keyword">fi</span>
    
    <span class="hljs-keyword">if</span> ! <span class="hljs-built_in">command</span> -v docker-compose &amp;&gt; /dev/null; <span class="hljs-keyword">then</span>
        log_error <span class="hljs-string">&quot;docker-compose未安装，请先安装docker-compose&quot;</span>
        <span class="hljs-built_in">exit</span> 1
    <span class="hljs-keyword">fi</span>
    
    log_info <span class="hljs-string">&quot;依赖检查完成&quot;</span>
}

<span class="hljs-comment"># 检查环境文件</span>
<span class="hljs-function"><span class="hljs-title">check_env_file</span></span>() {
    <span class="hljs-built_in">local</span> env_file=<span class="hljs-string">&quot;.env.<span class="hljs-variable">${ENV}</span>&quot;</span>
    
    <span class="hljs-keyword">if</span> [ ! -f <span class="hljs-string">&quot;<span class="hljs-variable">$env_file</span>&quot;</span> ]; <span class="hljs-keyword">then</span>
        log_warn <span class="hljs-string">&quot;环境文件 <span class="hljs-variable">$env_file</span> 不存在，使用默认配置&quot;</span>
        <span class="hljs-keyword">if</span> [ ! -f <span class="hljs-string">&quot;.env&quot;</span> ]; <span class="hljs-keyword">then</span>
            log_error <span class="hljs-string">&quot;未找到 .env 文件，请创建环境配置文件&quot;</span>
            <span class="hljs-built_in">exit</span> 1
        <span class="hljs-keyword">fi</span>
    <span class="hljs-keyword">else</span>
        log_info <span class="hljs-string">&quot;使用环境文件: <span class="hljs-variable">$env_file</span>&quot;</span>
        <span class="hljs-built_in">cp</span> <span class="hljs-string">&quot;<span class="hljs-variable">$env_file</span>&quot;</span> <span class="hljs-string">&quot;.env&quot;</span>
    <span class="hljs-keyword">fi</span>
}

<span class="hljs-comment"># 构建镜像</span>
<span class="hljs-function"><span class="hljs-title">build_image</span></span>() {
    log_info <span class="hljs-string">&quot;构建Docker镜像...&quot;</span>
    
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">&quot;<span class="hljs-variable">$ENV</span>&quot;</span> = <span class="hljs-string">&quot;prod&quot;</span> ]; <span class="hljs-keyword">then</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_PROD_FILE</span> build --no-cache
    <span class="hljs-keyword">else</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_FILE</span> build --no-cache
    <span class="hljs-keyword">fi</span>
    
    log_info <span class="hljs-string">&quot;镜像构建完成&quot;</span>
}

<span class="hljs-comment"># 启动服务</span>
<span class="hljs-function"><span class="hljs-title">start_services</span></span>() {
    log_info <span class="hljs-string">&quot;启动服务...&quot;</span>
    
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">&quot;<span class="hljs-variable">$ENV</span>&quot;</span> = <span class="hljs-string">&quot;prod&quot;</span> ]; <span class="hljs-keyword">then</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_PROD_FILE</span> up -d
    <span class="hljs-keyword">else</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_FILE</span> up -d
    <span class="hljs-keyword">fi</span>
    
    log_info <span class="hljs-string">&quot;等待服务启动...&quot;</span>
    <span class="hljs-built_in">sleep</span> 10
    
    <span class="hljs-comment"># 检查服务状态</span>
    check_services
}

<span class="hljs-comment"># 停止服务</span>
<span class="hljs-function"><span class="hljs-title">stop_services</span></span>() {
    log_info <span class="hljs-string">&quot;停止服务...&quot;</span>
    
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">&quot;<span class="hljs-variable">$ENV</span>&quot;</span> = <span class="hljs-string">&quot;prod&quot;</span> ]; <span class="hljs-keyword">then</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_PROD_FILE</span> down
    <span class="hljs-keyword">else</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_FILE</span> down
    <span class="hljs-keyword">fi</span>
    
    log_info <span class="hljs-string">&quot;服务已停止&quot;</span>
}

<span class="hljs-comment"># 重启服务</span>
<span class="hljs-function"><span class="hljs-title">restart_services</span></span>() {
    log_info <span class="hljs-string">&quot;重启服务...&quot;</span>
    stop_services
    start_services
}

<span class="hljs-comment"># 检查服务状态</span>
<span class="hljs-function"><span class="hljs-title">check_services</span></span>() {
    log_info <span class="hljs-string">&quot;检查服务状态...&quot;</span>
    
    <span class="hljs-comment"># 检查容器状态</span>
    <span class="hljs-keyword">if</span> docker-compose ps | grep -q <span class="hljs-string">&quot;Up&quot;</span>; <span class="hljs-keyword">then</span>
        log_info <span class="hljs-string">&quot;容器运行状态:&quot;</span>
        docker-compose ps
        
        <span class="hljs-comment"># 检查应用健康状态</span>
        log_info <span class="hljs-string">&quot;检查应用健康状态...&quot;</span>
        <span class="hljs-built_in">sleep</span> 5
        
        <span class="hljs-keyword">if</span> curl -f http://localhost:3000/api/health &amp;&gt; /dev/null; <span class="hljs-keyword">then</span>
            log_info <span class="hljs-string">&quot;应用健康检查通过&quot;</span>
        <span class="hljs-keyword">else</span>
            log_warn <span class="hljs-string">&quot;应用健康检查失败，请查看日志&quot;</span>
        <span class="hljs-keyword">fi</span>
    <span class="hljs-keyword">else</span>
        log_error <span class="hljs-string">&quot;容器未正常运行&quot;</span>
        docker-compose ps
        <span class="hljs-built_in">exit</span> 1
    <span class="hljs-keyword">fi</span>
}

<span class="hljs-comment"># 查看日志</span>
<span class="hljs-function"><span class="hljs-title">show_logs</span></span>() {
    log_info <span class="hljs-string">&quot;显示应用日志...&quot;</span>
    
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">&quot;<span class="hljs-variable">$ENV</span>&quot;</span> = <span class="hljs-string">&quot;prod&quot;</span> ]; <span class="hljs-keyword">then</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_PROD_FILE</span> logs -f --<span class="hljs-built_in">tail</span>=100
    <span class="hljs-keyword">else</span>
        docker-compose -f <span class="hljs-variable">$DOCKER_COMPOSE_FILE</span> logs -f --<span class="hljs-built_in">tail</span>=100
    <span class="hljs-keyword">fi</span>
}

<span class="hljs-comment"># 数据库初始化</span>
<span class="hljs-function"><span class="hljs-title">init_database</span></span>() {
    log_info <span class="hljs-string">&quot;初始化数据库...&quot;</span>
    
    <span class="hljs-comment"># 等待MySQL启动</span>
    log_info <span class="hljs-string">&quot;等待MySQL启动...&quot;</span>
    <span class="hljs-built_in">sleep</span> 15
    
    <span class="hljs-comment"># 执行初始化脚本</span>
    <span class="hljs-keyword">if</span> [ -f <span class="hljs-string">&quot;init.sql&quot;</span> ]; <span class="hljs-keyword">then</span>
        docker-compose <span class="hljs-built_in">exec</span> mysql mysql -uroot -p<span class="hljs-variable">${DB_PASSWORD}</span> &lt; init.sql
        log_info <span class="hljs-string">&quot;数据库初始化完成&quot;</span>
    <span class="hljs-keyword">else</span>
        log_warn <span class="hljs-string">&quot;未找到init.sql文件，跳过数据库初始化&quot;</span>
    <span class="hljs-keyword">fi</span>
}

<span class="hljs-comment"># 备份数据</span>
<span class="hljs-function"><span class="hljs-title">backup_data</span></span>() {
    log_info <span class="hljs-string">&quot;备份数据库...&quot;</span>
    
    <span class="hljs-built_in">local</span> backup_dir=<span class="hljs-string">&quot;./backups&quot;</span>
    <span class="hljs-built_in">local</span> backup_file=<span class="hljs-string">&quot;<span class="hljs-variable">${backup_dir}</span>/stock_monitor_<span class="hljs-subst">$(date +%Y%m%d_%H%M%S)</span>.sql&quot;</span>
    
    <span class="hljs-built_in">mkdir</span> -p <span class="hljs-string">&quot;<span class="hljs-variable">$backup_dir</span>&quot;</span>
    
    docker-compose <span class="hljs-built_in">exec</span> mysql mysqldump -uroot -p<span class="hljs-variable">${DB_PASSWORD}</span> stock_monitor &gt; <span class="hljs-string">&quot;<span class="hljs-variable">$backup_file</span>&quot;</span>
    
    log_info <span class="hljs-string">&quot;数据库备份完成: <span class="hljs-variable">$backup_file</span>&quot;</span>
}

<span class="hljs-comment"># 清理资源</span>
<span class="hljs-function"><span class="hljs-title">cleanup</span></span>() {
    log_info <span class="hljs-string">&quot;清理Docker资源...&quot;</span>
    
    <span class="hljs-comment"># 停止并删除容器</span>
    docker-compose down --remove-orphans
    
    <span class="hljs-comment"># 删除未使用的镜像</span>
    docker image prune -f
    
    <span class="hljs-comment"># 删除未使用的卷</span>
    docker volume prune -f
    
    log_info <span class="hljs-string">&quot;清理完成&quot;</span>
}

<span class="hljs-comment"># 显示帮助信息</span>
<span class="hljs-function"><span class="hljs-title">show_help</span></span>() {
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;股票监控系统部署脚本&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;使用方法:&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  <span class="hljs-variable">$0</span> [环境] [操作]&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;环境:&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  dev   - 开发环境 (默认)&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  prod  - 生产环境&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;操作:&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  build     - 构建镜像&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  start     - 启动服务 (默认)&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  stop      - 停止服务&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  restart   - 重启服务&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  logs      - 查看日志&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  status    - 检查状态&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  init      - 初始化数据库&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  backup    - 备份数据&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  cleanup   - 清理资源&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  help      - 显示帮助&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;示例:&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  <span class="hljs-variable">$0</span> dev start     # 开发环境启动&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  <span class="hljs-variable">$0</span> prod build    # 生产环境构建&quot;</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;  <span class="hljs-variable">$0</span> prod restart  # 生产环境重启&quot;</span>
}

<span class="hljs-comment"># 主函数</span>
<span class="hljs-function"><span class="hljs-title">main</span></span>() {
    <span class="hljs-keyword">case</span> <span class="hljs-variable">$ACTION</span> <span class="hljs-keyword">in</span>
        <span class="hljs-string">&quot;build&quot;</span>)
            check_dependencies
            check_env_file
            build_image
            ;;
        <span class="hljs-string">&quot;start&quot;</span>)
            check_dependencies
            check_env_file
            start_services
            ;;
        <span class="hljs-string">&quot;stop&quot;</span>)
            check_dependencies
            stop_services
            ;;
        <span class="hljs-string">&quot;restart&quot;</span>)
            check_dependencies
            check_env_file
            restart_services
            ;;
        <span class="hljs-string">&quot;logs&quot;</span>)
            show_logs
            ;;
        <span class="hljs-string">&quot;status&quot;</span>)
            check_services
            ;;
        <span class="hljs-string">&quot;init&quot;</span>)
            check_dependencies
            init_database
            ;;
        <span class="hljs-string">&quot;backup&quot;</span>)
            backup_data
            ;;
        <span class="hljs-string">&quot;cleanup&quot;</span>)
            cleanup
            ;;
        <span class="hljs-string">&quot;help&quot;</span>)
            show_help
            ;;
        *)
            log_error <span class="hljs-string">&quot;未知操作: <span class="hljs-variable">$ACTION</span>&quot;</span>
            show_help
            <span class="hljs-built_in">exit</span> 1
            ;;
    <span class="hljs-keyword">esac</span>
}

<span class="hljs-comment"># 执行主函数</span>
main
</code></pre>
<p><strong>docker-compose.prod.yml</strong></p>
<pre><code class="language-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">&#x27;3.8&#x27;</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">stock-monitor-nextjs:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile.prod</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;3000:3000&quot;</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NODE_ENV=production</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TZ=Asia/Shanghai</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USER=root</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=${DB_PASSWORD}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=stock_monitor</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=3306</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">mysql:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./logs:/app/logs</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">stock-monitor-network</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> [<span class="hljs-string">&quot;CMD&quot;</span>, <span class="hljs-string">&quot;curl&quot;</span>, <span class="hljs-string">&quot;-f&quot;</span>, <span class="hljs-string">&quot;http://localhost:3000/api/health&quot;</span>]
      <span class="hljs-attr">interval:</span> <span class="hljs-string">30s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">40s</span>

  <span class="hljs-attr">mysql:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:8.0</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MYSQL_ROOT_PASSWORD=${DB_PASSWORD}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MYSQL_DATABASE=stock_monitor</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TZ=Asia/Shanghai</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;3306:3306&quot;</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mysql_data:/var/lib/mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./init.sql:/docker-entrypoint-initdb.d/init.sql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./mysql-conf:/etc/mysql/conf.d</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">&gt;
      --default-time-zone=&#x27;+08:00&#x27;
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --max_connections=200
      --innodb_buffer_pool_size=256M
</span>    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">stock-monitor-network</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> [<span class="hljs-string">&quot;CMD&quot;</span>, <span class="hljs-string">&quot;mysqladmin&quot;</span>, <span class="hljs-string">&quot;ping&quot;</span>, <span class="hljs-string">&quot;-h&quot;</span>, <span class="hljs-string">&quot;localhost&quot;</span>, <span class="hljs-string">&quot;-u&quot;</span>, <span class="hljs-string">&quot;root&quot;</span>, <span class="hljs-string">&quot;-p${DB_PASSWORD}&quot;</span>]
      <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">5</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">30s</span>

  <span class="hljs-attr">nginx:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:alpine</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;80:80&quot;</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;443:443&quot;</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx.conf:/etc/nginx/nginx.conf</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./ssl:/etc/nginx/ssl</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">stock-monitor-nextjs</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">stock-monitor-network</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">mysql_data:</span>
    <span class="hljs-attr">driver:</span> <span class="hljs-string">local</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">stock-monitor-network:</span>
    <span class="hljs-attr">driver:</span> <span class="hljs-string">bridge</span>
</code></pre>
<p><strong>Dockerfile.prod</strong></p>
<pre><code class="language-dockerfile"><span class="hljs-comment"># 多阶段构建 - 生产环境优化版本</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine AS base

<span class="hljs-comment"># 安装依赖阶段</span>
<span class="hljs-keyword">FROM</span> base AS deps
<span class="hljs-keyword">RUN</span><span class="language-bash"> apk add --no-cache libc6-compat</span>
<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>

<span class="hljs-comment"># 复制package文件</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> package.json package-lock.json* ./</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> npm ci --only=production &amp;&amp; npm cache clean --force</span>

<span class="hljs-comment"># 构建阶段</span>
<span class="hljs-keyword">FROM</span> base AS builder
<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> --from=deps /app/node_modules ./node_modules</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> . .</span>

<span class="hljs-comment"># 设置环境变量</span>
<span class="hljs-keyword">ENV</span> NEXT_TELEMETRY_DISABLED <span class="hljs-number">1</span>
<span class="hljs-keyword">ENV</span> NODE_ENV production

<span class="hljs-comment"># 构建应用</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> npm run build</span>

<span class="hljs-comment"># 运行阶段</span>
<span class="hljs-keyword">FROM</span> base AS runner
<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>

<span class="hljs-keyword">ENV</span> NODE_ENV production
<span class="hljs-keyword">ENV</span> NEXT_TELEMETRY_DISABLED <span class="hljs-number">1</span>
<span class="hljs-keyword">ENV</span> TZ=Asia/Shanghai

<span class="hljs-comment"># 创建非root用户</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> addgroup --system --gid 1001 nodejs</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> adduser --system --uid 1001 nextjs</span>

<span class="hljs-comment"># 复制构建产物</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> --from=builder /app/public ./public</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> --from=builder --<span class="hljs-built_in">chown</span>=nextjs:nodejs /app/.next/standalone ./</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> --from=builder --<span class="hljs-built_in">chown</span>=nextjs:nodejs /app/.next/static ./.next/static</span>

<span class="hljs-comment"># 创建日志目录</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> <span class="hljs-built_in">mkdir</span> -p /app/logs &amp;&amp; <span class="hljs-built_in">chown</span> nextjs:nodejs /app/logs</span>

<span class="hljs-comment"># 切换到非root用户</span>
<span class="hljs-keyword">USER</span> nextjs

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>

<span class="hljs-keyword">ENV</span> PORT <span class="hljs-number">3000</span>
<span class="hljs-keyword">ENV</span> HOSTNAME <span class="hljs-string">&quot;0.0.0.0&quot;</span>

<span class="hljs-comment"># 健康检查</span>
<span class="hljs-keyword">HEALTHCHECK</span><span class="language-bash"> --interval=30s --<span class="hljs-built_in">timeout</span>=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/api/health || <span class="hljs-built_in">exit</span> 1</span>

<span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">&quot;node&quot;</span>, <span class="hljs-string">&quot;server.js&quot;</span>]</span>
</code></pre>
<h4 id="35-数据库连接和操作工具">3.5 数据库连接和操作工具</h4>
<p><strong>database.js</strong></p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> mysql = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;mysql2/promise&#x27;</span>);

<span class="hljs-comment">// 数据库连接池配置</span>
<span class="hljs-keyword">const</span> poolConfig = {
    <span class="hljs-attr">host</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_HOST</span> || <span class="hljs-string">&#x27;localhost&#x27;</span>,
    <span class="hljs-attr">user</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_USER</span> || <span class="hljs-string">&#x27;root&#x27;</span>,
    <span class="hljs-attr">password</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_PASSWORD</span>,
    <span class="hljs-attr">database</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_NAME</span> || <span class="hljs-string">&#x27;stock_monitor&#x27;</span>,
    <span class="hljs-attr">port</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_PORT</span> || <span class="hljs-number">3306</span>,
    <span class="hljs-attr">waitForConnections</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">connectionLimit</span>: <span class="hljs-number">10</span>,
    <span class="hljs-attr">queueLimit</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">timezone</span>: <span class="hljs-string">&#x27;+08:00&#x27;</span>, <span class="hljs-comment">// 设置为北京时间</span>
    <span class="hljs-attr">charset</span>: <span class="hljs-string">&#x27;utf8mb4&#x27;</span>
};

<span class="hljs-comment">// 创建连接池</span>
<span class="hljs-keyword">const</span> pool = mysql.<span class="hljs-title function_">createPool</span>(poolConfig);

<span class="hljs-comment">// 数据库操作工具类</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Database</span> {
    <span class="hljs-comment">// 执行查询</span>
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> <span class="hljs-title function_">query</span>(<span class="hljs-params">sql, params = []</span>) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> [rows] = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">execute</span>(sql, params);
            <span class="hljs-keyword">return</span> rows;
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;数据库查询错误:&#x27;</span>, error);
            <span class="hljs-keyword">throw</span> error;
        }
    }
    
    <span class="hljs-comment">// 执行事务</span>
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> <span class="hljs-title function_">transaction</span>(<span class="hljs-params">callback</span>) {
        <span class="hljs-keyword">const</span> connection = <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">getConnection</span>();
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> connection.<span class="hljs-title function_">beginTransaction</span>();
            <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title function_">callback</span>(connection);
            <span class="hljs-keyword">await</span> connection.<span class="hljs-title function_">commit</span>();
            <span class="hljs-keyword">return</span> result;
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-keyword">await</span> connection.<span class="hljs-title function_">rollback</span>();
            <span class="hljs-keyword">throw</span> error;
        } <span class="hljs-keyword">finally</span> {
            connection.<span class="hljs-title function_">release</span>();
        }
    }
    
    <span class="hljs-comment">// 检查数据库连接</span>
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> <span class="hljs-title function_">checkConnection</span>(<span class="hljs-params"></span>) {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">execute</span>(<span class="hljs-string">&#x27;SELECT 1&#x27;</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;数据库连接失败:&#x27;</span>, error);
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    }
    
    <span class="hljs-comment">// 关闭连接池</span>
    <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> <span class="hljs-title function_">close</span>(<span class="hljs-params"></span>) {
        <span class="hljs-keyword">await</span> pool.<span class="hljs-title function_">end</span>();
    }
}

<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">Database</span>;
</code></pre>
<h4 id="32-数据库操作">3.2 数据库操作</h4>
<ul>
<li><strong>连接池</strong>: 使用连接池管理数据库连接，最大连接数10</li>
<li><strong>重试机制</strong>: 数据库连接失败时自动重试，最多10次</li>
<li><strong>事务处理</strong>: 确保数据一致性</li>
<li><strong>时区设置</strong>: 数据库时区设置为+08:00</li>
</ul>
<h3 id="4-定时更新服务">4. 定时更新服务</h3>
<h4 id="41-交易时间判断">4.1 交易时间判断</h4>
<ul>
<li><strong>交易日</strong>: 周一至周五</li>
<li><strong>交易时间</strong>: 09:29 - 15:00</li>
<li><strong>更新策略</strong>:
<ul>
<li>交易时间内：每30秒更新一次</li>
<li>非交易时间：等待到下一个交易时间开始</li>
</ul>
</li>
</ul>
<h4 id="42-自动更新逻辑">4.2 自动更新逻辑</h4>
<ul>
<li><strong>启动时机</strong>: 服务器启动时自动开始</li>
<li><strong>更新流程</strong>:
<ol>
<li>检查当前是否为交易时间</li>
<li>如果是交易时间，批量获取所有股票数据</li>
<li>保存数据到数据库和缓存</li>
<li>设置下次更新时间</li>
</ol>
</li>
<li><strong>错误处理</strong>: 更新失败时记录日志并继续下次更新</li>
<li><strong>性能优化</strong>: 使用缓存减少数据库查询</li>
</ul>
<h3 id="5-api接口设计">5. API接口设计</h3>
<h4 id="51-股票管理接口">5.1 股票管理接口</h4>
<p><strong>获取股票列表</strong></p>
<pre><code>GET /api/stocks
Response: Stock[]
</code></pre>
<p><strong>添加股票</strong></p>
<pre><code>POST /api/stocks
Body: { code: string, name?: string }
Response: { id: number, code: string, name: string }
</code></pre>
<p><strong>删除股票</strong></p>
<pre><code>DELETE /api/stocks/:id
Response: { message: string }
</code></pre>
<h4 id="52-价格数据接口">5.2 价格数据接口</h4>
<p><strong>获取最新价格</strong></p>
<pre><code>GET /api/stocks/:id/price
Response: PriceData
</code></pre>
<p><strong>获取价格历史</strong></p>
<pre><code>GET /api/stocks/:id/history?limit=100&amp;offset=0&amp;startDate=&amp;endDate=
Response: { data: PriceData[], total: number, limit: number, offset: number }
</code></pre>
<p><strong>获取当日价格</strong></p>
<pre><code>GET /api/stocks/:id/today
Response: PriceData[]
</code></pre>
<p><strong>手动更新所有股票</strong></p>
<pre><code>POST /api/stocks/update
Response: { message: string }
</code></pre>
<h3 id="6-前端界面需求">6. 前端界面需求</h3>
<h4 id="61-主页面布局">6.1 主页面布局</h4>
<ul>
<li><strong>页面标题</strong>: &quot;股票监控系统&quot;</li>
<li><strong>添加股票表单</strong>:
<ul>
<li>股票代码输入框（必填）</li>
<li>股票名称输入框（可选）</li>
<li>添加按钮</li>
</ul>
</li>
<li><strong>股票列表区域</strong>: 以卡片形式展示所有股票</li>
</ul>
<h4 id="62-股票卡片设计">6.2 股票卡片设计</h4>
<ul>
<li><strong>基本信息</strong>: 股票名称、代码</li>
<li><strong>价格信息</strong>:
<ul>
<li>当前价格（大字体显示）</li>
<li>涨跌金额和涨跌幅（红涨绿跌）</li>
<li>开盘价、最高价、最低价</li>
<li>最高涨幅、最低涨幅</li>
<li>更新时间</li>
</ul>
</li>
<li><strong>操作按钮</strong>: 详情按钮、删除按钮</li>
</ul>
<h4 id="63-股票详情页面">6.3 股票详情页面</h4>
<ul>
<li><strong>URL路由</strong>: <code>/stock/:id</code></li>
<li><strong>页面内容</strong>:
<ul>
<li>股票基本信息</li>
<li>最新价格数据</li>
<li>价格历史表格（支持分页）</li>
<li>日期筛选功能</li>
<li>统计信息（最高涨幅、最高跌幅）</li>
</ul>
</li>
</ul>
<h4 id="64-响应式设计">6.4 响应式设计</h4>
<ul>
<li><strong>桌面端</strong>: 多列卡片布局</li>
<li><strong>移动端</strong>: 单列卡片布局</li>
<li><strong>适配屏幕</strong>: 支持各种屏幕尺寸</li>
</ul>
<h3 id="7-用户体验需求">7. 用户体验需求</h3>
<h4 id="71-实时更新">7.1 实时更新</h4>
<ul>
<li><strong>自动刷新</strong>: 页面每10秒自动刷新股票数据</li>
<li><strong>加载状态</strong>: 显示数据加载中的提示</li>
<li><strong>错误处理</strong>: 网络错误时显示友好提示</li>
</ul>
<h4 id="72-交互反馈">7.2 交互反馈</h4>
<ul>
<li><strong>操作确认</strong>: 删除股票前需要用户确认</li>
<li><strong>成功提示</strong>: 操作成功后显示提示信息</li>
<li><strong>错误提示</strong>: 操作失败时显示具体错误信息</li>
</ul>
<h4 id="73-性能优化">7.3 性能优化</h4>
<ul>
<li><strong>数据缓存</strong>: 合理使用缓存减少API调用</li>
<li><strong>懒加载</strong>: 大量数据时使用分页加载</li>
<li><strong>防抖处理</strong>: 避免频繁的用户操作</li>
</ul>
<h3 id="8-错误处理和容错机制">8. 错误处理和容错机制</h3>
<h4 id="81-api错误处理">8.1 API错误处理</h4>
<ul>
<li><strong>网络错误</strong>: 显示网络连接失败提示</li>
<li><strong>服务器错误</strong>: 显示服务器错误提示</li>
<li><strong>数据格式错误</strong>: 使用默认值或跳过错误数据</li>
</ul>
<h4 id="82-数据库错误处理">8.2 数据库错误处理</h4>
<ul>
<li><strong>连接失败</strong>: 使用缓存数据或模拟数据</li>
<li><strong>查询失败</strong>: 记录错误日志并返回空结果</li>
<li><strong>写入失败</strong>: 重试机制或降级处理</li>
</ul>
<h4 id="83-外部api错误处理">8.3 外部API错误处理</h4>
<ul>
<li><strong>API限流</strong>: 增加请求间隔</li>
<li><strong>数据解析失败</strong>: 使用模拟数据作为备用</li>
<li><strong>股票代码错误</strong>: 提示用户检查代码格式</li>
</ul>
<h3 id="9-安全性要求">9. 安全性要求</h3>
<h4 id="91-输入验证">9.1 输入验证</h4>
<ul>
<li><strong>股票代码</strong>: 验证格式（6位数字）</li>
<li><strong>SQL注入</strong>: 使用参数化查询</li>
<li><strong>XSS防护</strong>: 对用户输入进行转义</li>
</ul>
<h4 id="92-api安全">9.2 API安全</h4>
<ul>
<li><strong>CORS配置</strong>: 合理配置跨域访问</li>
<li><strong>请求限制</strong>: 防止恶意频繁请求</li>
<li><strong>错误信息</strong>: 不暴露敏感系统信息</li>
</ul>
<h3 id="10-部署和环境配置">10. 部署和环境配置</h3>
<h4 id="101-docker-部署方式">10.1 Docker 部署方式</h4>
<p><strong>重要说明</strong>：使用原有的Docker部署方式，保持与现有系统的一致性。</p>
<p><strong>Dockerfile</strong></p>
<pre><code class="language-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine

<span class="hljs-keyword">WORKDIR</span><span class="language-bash"> /app</span>

<span class="hljs-comment"># 复制package文件</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> package*.json ./</span>

<span class="hljs-comment"># 安装依赖</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> npm ci --only=production</span>

<span class="hljs-comment"># 复制源代码</span>
<span class="hljs-keyword">COPY</span><span class="language-bash"> . .</span>

<span class="hljs-comment"># 构建Next.js应用</span>
<span class="hljs-keyword">RUN</span><span class="language-bash"> npm run build</span>

<span class="hljs-comment"># 暴露端口</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>

<span class="hljs-comment"># 启动应用</span>
<span class="hljs-keyword">CMD</span><span class="language-bash"> [<span class="hljs-string">&quot;npm&quot;</span>, <span class="hljs-string">&quot;start&quot;</span>]</span>
</code></pre>
<p><strong>docker-compose.yml</strong></p>
<pre><code class="language-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">&#x27;3.8&#x27;</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">stock-monitor-nextjs:</span>
    <span class="hljs-attr">build:</span> <span class="hljs-string">.</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;3000:3000&quot;</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NODE_ENV=production</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TZ=Asia/Shanghai</span>  <span class="hljs-comment"># 设置容器时区为北京时间</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_USER=root</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PASSWORD=${DB_PASSWORD}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_NAME=stock_monitor</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mysql</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./logs:/app/logs</span>

  <span class="hljs-attr">mysql:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:8.0</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MYSQL_ROOT_PASSWORD=${DB_PASSWORD}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">MYSQL_DATABASE=stock_monitor</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">TZ=Asia/Shanghai</span>  <span class="hljs-comment"># 设置MySQL时区为北京时间</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&quot;3306:3306&quot;</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mysql_data:/var/lib/mysql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./init.sql:/docker-entrypoint-initdb.d/init.sql</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">--default-time-zone=&#x27;+08:00&#x27;</span>  <span class="hljs-comment"># 设置MySQL默认时区</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">mysql_data:</span>
</code></pre>
<h4 id="102-环境变量配置">10.2 环境变量配置</h4>
<p><strong>.env.example</strong></p>
<pre><code class="language-env"># 数据库配置（使用原有数据库）
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=stock_monitor
DB_PORT=3306

# 应用配置
PORT=3000
NODE_ENV=production

# 时区配置
TZ=Asia/Shanghai

# 股票API配置
STOCK_API_BASE_URL=https://qt.gtimg.cn
STOCK_UPDATE_INTERVAL=10000

# 日志配置
LOG_LEVEL=info
LOG_FILE_PATH=./logs/app.log
</code></pre>
<h4 id="103-nextjs-配置">10.3 Next.js 配置</h4>
<pre><code class="language-javascript"><span class="hljs-comment">// next.config.js</span>
<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = {
  <span class="hljs-attr">env</span>: {
    <span class="hljs-attr">DB_HOST</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_HOST</span>,
    <span class="hljs-attr">DB_USER</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_USER</span>,
    <span class="hljs-attr">DB_PASSWORD</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_PASSWORD</span>,
    <span class="hljs-attr">DB_NAME</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_NAME</span>,
    <span class="hljs-attr">DB_PORT</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">DB_PORT</span>,
    <span class="hljs-attr">TZ</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">TZ</span> || <span class="hljs-string">&#x27;Asia/Shanghai&#x27;</span>,
  },
  <span class="hljs-attr">experimental</span>: {
    <span class="hljs-attr">serverComponentsExternalPackages</span>: [<span class="hljs-string">&#x27;mysql2&#x27;</span>]
  },
  <span class="hljs-comment">// 时区配置</span>
  <span class="hljs-attr">webpack</span>: <span class="hljs-function">(<span class="hljs-params">config</span>) =&gt;</span> {
    config.<span class="hljs-property">resolve</span>.<span class="hljs-property">fallback</span> = {
      ...config.<span class="hljs-property">resolve</span>.<span class="hljs-property">fallback</span>,
      <span class="hljs-attr">fs</span>: <span class="hljs-literal">false</span>,
    };
    <span class="hljs-keyword">return</span> config;
  },
}
</code></pre>
<h4 id="104-依赖包">10.4 依赖包</h4>
<ul>
<li><strong>生产依赖</strong>:
<ul>
<li>next: React框架</li>
<li>react: UI库</li>
<li>mysql2: MySQL驱动</li>
<li>axios: HTTP客户端</li>
<li>iconv-lite: 编码转换</li>
</ul>
</li>
<li><strong>开发依赖</strong>:
<ul>
<li>typescript: 类型支持</li>
<li>@types/*: 类型定义</li>
</ul>
</li>
</ul>
<h4 id="105-构建和启动">10.5 构建和启动</h4>
<pre><code class="language-bash"><span class="hljs-comment"># Docker方式部署</span>
docker-compose up -d

<span class="hljs-comment"># 传统方式</span>
npm install
npm run build
npm start
</code></pre>
<h3 id="11-数据类型定义">11. 数据类型定义</h3>
<h4 id="111-typescript接口">11.1 TypeScript接口</h4>
<pre><code class="language-typescript"><span class="hljs-comment">// 股票基本信息</span>
<span class="hljs-keyword">interface</span> <span class="hljs-title class_">Stock</span> {
  <span class="hljs-attr">id</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">code</span>: <span class="hljs-built_in">string</span>;
  <span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>;
  <span class="hljs-attr">created_at</span>: <span class="hljs-built_in">string</span>;
}

<span class="hljs-comment">// 价格数据</span>
<span class="hljs-keyword">interface</span> <span class="hljs-title class_">PriceData</span> {
  id?: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">stock_id</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">price</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">open_price</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">close_price</span>: <span class="hljs-built_in">number</span> | <span class="hljs-literal">null</span>;
  <span class="hljs-attr">high_price</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">low_price</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">timestamp</span>: <span class="hljs-built_in">string</span>;
}

<span class="hljs-comment">// API响应格式</span>
<span class="hljs-keyword">interface</span> <span class="hljs-title class_">ApiResponse</span>&lt;T&gt; {
  <span class="hljs-attr">data</span>: T;
  message?: <span class="hljs-built_in">string</span>;
  error?: <span class="hljs-built_in">string</span>;
}

<span class="hljs-comment">// 分页数据</span>
<span class="hljs-keyword">interface</span> <span class="hljs-title class_">PaginatedData</span>&lt;T&gt; {
  <span class="hljs-attr">data</span>: T[];
  <span class="hljs-attr">total</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">limit</span>: <span class="hljs-built_in">number</span>;
  <span class="hljs-attr">offset</span>: <span class="hljs-built_in">number</span>;
}
</code></pre>
<h3 id="12-特殊功能需求">12. 特殊功能需求</h3>
<h4 id="121-模拟数据支持">12.1 模拟数据支持</h4>
<ul>
<li><strong>使用场景</strong>: API获取失败或数据库连接失败时</li>
<li><strong>模拟数据</strong>: 生成随机价格数据用于演示</li>
<li><strong>数据格式</strong>: 与真实数据格式完全一致</li>
</ul>
<h4 id="122-时区处理规范">12.2 时区处理规范</h4>
<p><strong>重要要求</strong>：所有时间相关操作都必须先验证是否为北京时间，然后再进行判断。</p>
<p><strong>时间验证流程</strong></p>
<ol>
<li><strong>时间验证</strong>：检查输入时间是否为北京时间（UTC+8）</li>
<li><strong>时间转换</strong>：如果不是北京时间，自动转换为北京时间</li>
<li><strong>时间存储</strong>：数据库中同时存储UTC时间戳和北京时间</li>
<li><strong>时间显示</strong>：前端统一显示北京时间</li>
</ol>
<p><strong>实现要求</strong></p>
<pre><code class="language-javascript"><span class="hljs-comment">// 时间验证和处理工具类</span>
<span class="hljs-keyword">class</span> <span class="hljs-title class_">TimeUtils</span> {
    <span class="hljs-comment">// 验证并转换为北京时间</span>
    <span class="hljs-keyword">static</span> <span class="hljs-title function_">validateAndConvertToBeijingTime</span>(<span class="hljs-params">timestamp</span>) {
        <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(timestamp);
        <span class="hljs-keyword">const</span> offset = date.<span class="hljs-title function_">getTimezoneOffset</span>();
        
        <span class="hljs-comment">// 检查是否为北京时间（UTC+8，即-480分钟）</span>
        <span class="hljs-keyword">if</span> (offset !== -<span class="hljs-number">480</span>) {
            <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">warn</span>(<span class="hljs-string">`时间不是北京时间，正在转换: <span class="hljs-subst">${timestamp}</span>`</span>);
            <span class="hljs-comment">// 转换为北京时间</span>
            date.<span class="hljs-title function_">setMinutes</span>(date.<span class="hljs-title function_">getMinutes</span>() - offset - <span class="hljs-number">480</span>);
        }
        
        <span class="hljs-keyword">return</span> date;
    }
    
    <span class="hljs-comment">// 获取当前北京时间</span>
    <span class="hljs-keyword">static</span> <span class="hljs-title function_">getCurrentBeijingTime</span>(<span class="hljs-params"></span>) {
        <span class="hljs-keyword">const</span> now = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>();
        <span class="hljs-keyword">return</span> <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">validateAndConvertToBeijingTime</span>(now);
    }
    
    <span class="hljs-comment">// 判断是否为交易时间（基于北京时间）</span>
    <span class="hljs-keyword">static</span> <span class="hljs-title function_">isTradingTime</span>(<span class="hljs-params">beijingTime = <span class="hljs-literal">null</span></span>) {
        <span class="hljs-keyword">const</span> time = beijingTime || <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">getCurrentBeijingTime</span>();
        <span class="hljs-keyword">const</span> day = time.<span class="hljs-title function_">getDay</span>();
        <span class="hljs-keyword">const</span> hour = time.<span class="hljs-title function_">getHours</span>();
        <span class="hljs-keyword">const</span> minute = time.<span class="hljs-title function_">getMinutes</span>();
        
        <span class="hljs-comment">// 工作日判断</span>
        <span class="hljs-keyword">if</span> (day === <span class="hljs-number">0</span> || day === <span class="hljs-number">6</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        
        <span class="hljs-comment">// 交易时间：9:30-15:00（北京时间）</span>
        <span class="hljs-keyword">const</span> currentMinutes = hour * <span class="hljs-number">60</span> + minute;
        <span class="hljs-keyword">const</span> startMinutes = <span class="hljs-number">9</span> * <span class="hljs-number">60</span> + <span class="hljs-number">30</span>; <span class="hljs-comment">// 9:30</span>
        <span class="hljs-keyword">const</span> endMinutes = <span class="hljs-number">15</span> * <span class="hljs-number">60</span>; <span class="hljs-comment">// 15:00</span>
        
        <span class="hljs-keyword">return</span> currentMinutes &gt;= startMinutes &amp;&amp; currentMinutes &lt; endMinutes;
    }
    
    <span class="hljs-comment">// 格式化为MySQL DATETIME格式</span>
    <span class="hljs-keyword">static</span> <span class="hljs-title function_">formatForMySQL</span>(<span class="hljs-params">beijingTime</span>) {
        <span class="hljs-keyword">return</span> beijingTime.<span class="hljs-title function_">toISOString</span>().<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-number">19</span>).<span class="hljs-title function_">replace</span>(<span class="hljs-string">&#x27;T&#x27;</span>, <span class="hljs-string">&#x27; &#x27;</span>);
    }
}
</code></pre>
<p><strong>数据库时间字段</strong></p>
<ul>
<li><code>timestamp</code>：UTC时间戳（保持兼容性）</li>
<li><code>beijing_time</code>：北京时间（DATETIME格式）</li>
<li>所有查询和显示都基于<code>beijing_time</code>字段</li>
</ul>
<p><strong>交易时间规则</strong></p>
<ul>
<li><strong>统一时区</strong>: 所有时间显示使用北京时间</li>
<li><strong>时间格式</strong>: YYYY-MM-DD HH:mm:ss</li>
<li><strong>交易时间</strong>: 基于北京时间判断（工作日 9:30-15:00）</li>
<li><strong>数据更新频率</strong>: 交易时间内每10秒更新一次</li>
</ul>
<h4 id="123-数据统计">12.3 数据统计</h4>
<ul>
<li><strong>涨跌幅计算</strong>: 基于开盘价或昨日收盘价</li>
<li><strong>最高/最低涨幅</strong>: 基于开盘价计算</li>
<li><strong>价格变化</strong>: 实时计算并显示</li>
</ul>
<h3 id="13-性能指标">13. 性能指标</h3>
<h4 id="131-响应时间">13.1 响应时间</h4>
<ul>
<li><strong>页面加载</strong>: &lt; 2秒</li>
<li><strong>API响应</strong>: &lt; 1秒</li>
<li><strong>数据更新</strong>: &lt; 30秒</li>
</ul>
<h4 id="132-并发处理">13.2 并发处理</h4>
<ul>
<li><strong>同时用户</strong>: 支持100+并发用户</li>
<li><strong>数据库连接</strong>: 连接池管理，最大10个连接</li>
<li><strong>API限流</strong>: 合理控制外部API调用频率</li>
</ul>
<h3 id="14-监控和日志">14. 监控和日志</h3>
<h4 id="141-健康检查api">14.1 健康检查API</h4>
<p><strong>pages/api/health.js</strong></p>
<pre><code class="language-javascript"><span class="hljs-keyword">import</span> <span class="hljs-title class_">Database</span> <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;../../lib/database&#x27;</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">TimeUtils</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;../../lib/timeUtils&#x27;</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">handler</span>(<span class="hljs-params">req, res</span>) {
    <span class="hljs-keyword">if</span> (req.<span class="hljs-property">method</span> !== <span class="hljs-string">&#x27;GET&#x27;</span>) {
        <span class="hljs-keyword">return</span> res.<span class="hljs-title function_">status</span>(<span class="hljs-number">405</span>).<span class="hljs-title function_">json</span>({ <span class="hljs-attr">error</span>: <span class="hljs-string">&#x27;Method not allowed&#x27;</span> });
    }
    
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// 检查数据库连接</span>
        <span class="hljs-keyword">const</span> dbStatus = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Database</span>.<span class="hljs-title function_">checkConnection</span>();
        
        <span class="hljs-comment">// 检查时间同步</span>
        <span class="hljs-keyword">const</span> currentTime = <span class="hljs-title class_">TimeUtils</span>.<span class="hljs-title function_">getCurrentBeijingTime</span>();
        <span class="hljs-keyword">const</span> timeStatus = <span class="hljs-title class_">TimeUtils</span>.<span class="hljs-title function_">validateAndConvertToBeijingTime</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>());
        
        <span class="hljs-comment">// 检查股票服务状态</span>
        <span class="hljs-keyword">const</span> stockServiceStatus = <span class="hljs-keyword">await</span> <span class="hljs-title function_">checkStockService</span>();
        
        <span class="hljs-keyword">const</span> health = {
            <span class="hljs-attr">status</span>: <span class="hljs-string">&#x27;healthy&#x27;</span>,
            <span class="hljs-attr">timestamp</span>: currentTime.<span class="hljs-title function_">toISOString</span>(),
            <span class="hljs-attr">beijing_time</span>: <span class="hljs-title class_">TimeUtils</span>.<span class="hljs-title function_">formatForMySQL</span>(currentTime),
            <span class="hljs-attr">services</span>: {
                <span class="hljs-attr">database</span>: dbStatus ? <span class="hljs-string">&#x27;healthy&#x27;</span> : <span class="hljs-string">&#x27;unhealthy&#x27;</span>,
                <span class="hljs-attr">stock_service</span>: stockServiceStatus ? <span class="hljs-string">&#x27;healthy&#x27;</span> : <span class="hljs-string">&#x27;unhealthy&#x27;</span>,
                <span class="hljs-attr">time_sync</span>: timeStatus ? <span class="hljs-string">&#x27;healthy&#x27;</span> : <span class="hljs-string">&#x27;unhealthy&#x27;</span>
            },
            <span class="hljs-attr">version</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">npm_package_version</span> || <span class="hljs-string">&#x27;1.0.0&#x27;</span>,
            <span class="hljs-attr">environment</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">NODE_ENV</span> || <span class="hljs-string">&#x27;development&#x27;</span>
        };
        
        <span class="hljs-keyword">const</span> overallStatus = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">values</span>(health.<span class="hljs-property">services</span>).<span class="hljs-title function_">every</span>(<span class="hljs-function"><span class="hljs-params">status</span> =&gt;</span> status === <span class="hljs-string">&#x27;healthy&#x27;</span>);
        
        res.<span class="hljs-title function_">status</span>(overallStatus ? <span class="hljs-number">200</span> : <span class="hljs-number">503</span>).<span class="hljs-title function_">json</span>(health);
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(<span class="hljs-string">&#x27;健康检查失败:&#x27;</span>, error);
        res.<span class="hljs-title function_">status</span>(<span class="hljs-number">503</span>).<span class="hljs-title function_">json</span>({
            <span class="hljs-attr">status</span>: <span class="hljs-string">&#x27;unhealthy&#x27;</span>,
            <span class="hljs-attr">error</span>: error.<span class="hljs-property">message</span>,
            <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>().<span class="hljs-title function_">toISOString</span>()
        });
    }
}

<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">checkStockService</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// 检查股票服务是否正常</span>
        <span class="hljs-keyword">const</span> testStock = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Database</span>.<span class="hljs-title function_">query</span>(<span class="hljs-string">&#x27;SELECT COUNT(*) as count FROM stocks LIMIT 1&#x27;</span>);
        <span class="hljs-keyword">return</span> testStock.<span class="hljs-property">length</span> &gt; <span class="hljs-number">0</span>;
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
}
</code></pre>
<h4 id="142-日志配置">14.2 日志配置</h4>
<p><strong>lib/logger.js</strong></p>
<pre><code class="language-javascript"><span class="hljs-keyword">const</span> winston = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;winston&#x27;</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">&#x27;path&#x27;</span>);

<span class="hljs-comment">// 日志格式</span>
<span class="hljs-keyword">const</span> logFormat = winston.<span class="hljs-property">format</span>.<span class="hljs-title function_">combine</span>(
    winston.<span class="hljs-property">format</span>.<span class="hljs-title function_">timestamp</span>({
        <span class="hljs-attr">format</span>: <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-comment">// 确保日志时间为北京时间</span>
            <span class="hljs-keyword">const</span> beijingTime = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>();
            beijingTime.<span class="hljs-title function_">setHours</span>(beijingTime.<span class="hljs-title function_">getHours</span>() + <span class="hljs-number">8</span>);
            <span class="hljs-keyword">return</span> beijingTime.<span class="hljs-title function_">toISOString</span>().<span class="hljs-title function_">replace</span>(<span class="hljs-string">&#x27;T&#x27;</span>, <span class="hljs-string">&#x27; &#x27;</span>).<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-number">19</span>);
        }
    }),
    winston.<span class="hljs-property">format</span>.<span class="hljs-title function_">errors</span>({ <span class="hljs-attr">stack</span>: <span class="hljs-literal">true</span> }),
    winston.<span class="hljs-property">format</span>.<span class="hljs-title function_">json</span>()
);

<span class="hljs-comment">// 创建logger</span>
<span class="hljs-keyword">const</span> logger = winston.<span class="hljs-title function_">createLogger</span>({
    <span class="hljs-attr">level</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">LOG_LEVEL</span> || <span class="hljs-string">&#x27;info&#x27;</span>,
    <span class="hljs-attr">format</span>: logFormat,
    <span class="hljs-attr">defaultMeta</span>: {
        <span class="hljs-attr">service</span>: <span class="hljs-string">&#x27;stock-monitor&#x27;</span>,
        <span class="hljs-attr">environment</span>: process.<span class="hljs-property">env</span>.<span class="hljs-property">NODE_ENV</span> || <span class="hljs-string">&#x27;development&#x27;</span>
    },
    <span class="hljs-attr">transports</span>: [
        <span class="hljs-comment">// 错误日志</span>
        <span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">File</span>({
            <span class="hljs-attr">filename</span>: path.<span class="hljs-title function_">join</span>(process.<span class="hljs-title function_">cwd</span>(), <span class="hljs-string">&#x27;logs&#x27;</span>, <span class="hljs-string">&#x27;error.log&#x27;</span>),
            <span class="hljs-attr">level</span>: <span class="hljs-string">&#x27;error&#x27;</span>,
            <span class="hljs-attr">maxsize</span>: <span class="hljs-number">5242880</span>, <span class="hljs-comment">// 5MB</span>
            <span class="hljs-attr">maxFiles</span>: <span class="hljs-number">5</span>
        }),
        <span class="hljs-comment">// 所有日志</span>
        <span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">File</span>({
            <span class="hljs-attr">filename</span>: path.<span class="hljs-title function_">join</span>(process.<span class="hljs-title function_">cwd</span>(), <span class="hljs-string">&#x27;logs&#x27;</span>, <span class="hljs-string">&#x27;combined.log&#x27;</span>),
            <span class="hljs-attr">maxsize</span>: <span class="hljs-number">5242880</span>, <span class="hljs-comment">// 5MB</span>
            <span class="hljs-attr">maxFiles</span>: <span class="hljs-number">10</span>
        }),
        <span class="hljs-comment">// 控制台输出</span>
        <span class="hljs-keyword">new</span> winston.<span class="hljs-property">transports</span>.<span class="hljs-title class_">Console</span>({
            <span class="hljs-attr">format</span>: winston.<span class="hljs-property">format</span>.<span class="hljs-title function_">combine</span>(
                winston.<span class="hljs-property">format</span>.<span class="hljs-title function_">colorize</span>(),
                winston.<span class="hljs-property">format</span>.<span class="hljs-title function_">simple</span>()
            )
        })
    ]
});

<span class="hljs-comment">// 时间相关日志</span>
logger.<span class="hljs-property">timeLog</span> = <span class="hljs-function">(<span class="hljs-params">message, data = {}</span>) =&gt;</span> {
    logger.<span class="hljs-title function_">info</span>(message, {
        ...data,
        <span class="hljs-attr">beijing_time</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>().<span class="hljs-title function_">toLocaleString</span>(<span class="hljs-string">&#x27;zh-CN&#x27;</span>, { <span class="hljs-attr">timeZone</span>: <span class="hljs-string">&#x27;Asia/Shanghai&#x27;</span> }),
        <span class="hljs-attr">timezone_offset</span>: <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>().<span class="hljs-title function_">getTimezoneOffset</span>()
    });
};

<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = logger;
</code></pre>
<h4 id="143-日志记录规范">14.3 日志记录规范</h4>
<ul>
<li><strong>操作日志</strong>: 记录用户操作（添加、删除股票）</li>
<li><strong>错误日志</strong>: 记录系统错误和异常</li>
<li><strong>性能日志</strong>: 记录API响应时间和数据库查询时间</li>
<li><strong>时间日志</strong>: 所有日志统一使用北京时间</li>
</ul>
<h4 id="144-监控指标">14.4 监控指标</h4>
<ul>
<li><strong>系统状态</strong>: 服务器运行状态</li>
<li><strong>数据库状态</strong>: 连接状态和查询性能</li>
<li><strong>外部API状态</strong>: 调用成功率和响应时间</li>
<li><strong>时间同步状态</strong>: 确保所有时间操作基于北京时间</li>
</ul>
<h4 id="145-docker日志配置">14.5 Docker日志配置</h4>
<p>在docker-compose.yml中添加日志配置：</p>
<pre><code class="language-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">stock-monitor-nextjs:</span>
    <span class="hljs-comment"># ... 其他配置</span>
    <span class="hljs-attr">logging:</span>
      <span class="hljs-attr">driver:</span> <span class="hljs-string">&quot;json-file&quot;</span>
      <span class="hljs-attr">options:</span>
        <span class="hljs-attr">max-size:</span> <span class="hljs-string">&quot;10m&quot;</span>
        <span class="hljs-attr">max-file:</span> <span class="hljs-string">&quot;3&quot;</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./logs:/app/logs</span>
      
  <span class="hljs-attr">mysql:</span>
    <span class="hljs-comment"># ... 其他配置</span>
    <span class="hljs-attr">logging:</span>
      <span class="hljs-attr">driver:</span> <span class="hljs-string">&quot;json-file&quot;</span>
      <span class="hljs-attr">options:</span>
        <span class="hljs-attr">max-size:</span> <span class="hljs-string">&quot;10m&quot;</span>
        <span class="hljs-attr">max-file:</span> <span class="hljs-string">&quot;3&quot;</span>
</code></pre>
<h3 id="15-扩展性考虑">15. 扩展性考虑</h3>
<h4 id="151-功能扩展">15.1 功能扩展</h4>
<ul>
<li><strong>更多数据源</strong>: 支持其他股票API</li>
<li><strong>技术指标</strong>: 添加MA、MACD等技术分析</li>
<li><strong>价格预警</strong>: 设置价格提醒功能</li>
<li><strong>数据导出</strong>: 支持Excel、CSV导出</li>
</ul>
<h4 id="152-架构扩展">15.2 架构扩展</h4>
<ul>
<li><strong>微服务</strong>: 拆分为独立的服务模块</li>
<li><strong>缓存层</strong>: 添加Redis缓存</li>
<li><strong>消息队列</strong>: 异步处理数据更新</li>
<li><strong>负载均衡</strong>: 支持多实例部署</li>
</ul>
<hr>
<h2 id="实现优先级">实现优先级</h2>
<h3 id="第一阶段核心功能">第一阶段（核心功能）</h3>
<ol>
<li>数据库设计和连接</li>
<li>基本的股票管理API</li>
<li>腾讯API数据获取</li>
<li>简单的前端界面</li>
</ol>
<h3 id="第二阶段完善功能">第二阶段（完善功能）</h3>
<ol>
<li>定时更新服务</li>
<li>价格历史查询</li>
<li>股票详情页面</li>
<li>错误处理和容错</li>
</ol>
<h3 id="第三阶段优化功能">第三阶段（优化功能）</h3>
<ol>
<li>性能优化和缓存</li>
<li>响应式设计</li>
<li>用户体验优化</li>
<li>监控和日志</li>
</ol>
<hr>
<p><strong>注意事项</strong>:</p>
<ol>
<li>所有时间处理统一使用北京时间（UTC+8）</li>
<li>股票代码格式化要正确处理市场前缀（sz/sh）</li>
<li>外部API调用要有合理的错误处理和重试机制</li>
<li>数据库操作要考虑并发安全和事务一致性</li>
<li>前端要有良好的加载状态和错误提示</li>
<li>代码要有清晰的注释和类型定义</li>
</ol>
<p>这份需求文档涵盖了原系统的所有核心功能和技术细节，可以作为在Next.js框架下重新实现的完整指南。</p>

            <script async src="https://cdn.jsdelivr.net/npm/katex-copytex@latest/dist/katex-copytex.min.js"></script>
            
        </body>
        </html>